/**
 *  @file os.c
 *
 *  Operating system functions used by uCIP.
 *
 *  System specific stuff for ISS/OS running on the ls808.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pthread.h>
#include <xmalloc.h>
#include <gpio_irq.h>
#include <ls588.h>

#include "../netconf.h"
#include "../netbuf.h"
#include "../net.h"
#include "../netaddrs.h"
#include "../netifdev.h"
#include "../netether.h"
#include "../nettimer.h"

#include "os.h"


/*################## uC/OS ###################*/
ULONG OSTimeGet()
{
	return mtime();
}

/*------ semaphores ------*/
OS_EVENT *OSSemCreate(UWORD value)
{
	sem_t *s=(sem_t*)xmalloc(sizeof(sem_t),MT_NETSEMAPHORE,0);
	if(!s)
	{
		panic("net_adl: out of memory allocating a semaphore\n");
		return NULL;
	}
	sem_init(s, 0, value);
	return (OS_EVENT*)s;
}

void OSSemDestroy(OS_EVENT *e)
{
	sem_destroy((sem_t*)e);
	free(e);
}

typedef struct {
	UI32 mainId;
	UI32 timeout;
} T_OSSemInfo;

void timeoutSemPend(void*v)
{
	UI32 t=mtime();

	// wait for timeout
	while(abs(diffTime(t))<((T_OSSemInfo*)v)->timeout)
		sleep(1);

	printf("semaphore timed out\n");

	((T_OSSemInfo*)v)->timeout=0;
	threadWake(((T_OSSemInfo*)v)->mainId);
}

void OSSemPend(OS_EVENT *pevent, UWORD timeout,UBYTE* err)
{
	UI32 timeoutCheckId=0;
	int wasCritical=0;
	T_OSSemInfo i;

	if(timeout)
	{
		i.timeout=timeout;
		i.mainId=threadId();
		timeoutCheckId=newThreadName(timeoutSemPend,5000,(void*)&i,"timeoutSemPend");
	}

#if 0
	if(inCritical)
	{
		int i;
		printf("!!!!!!!!!!!!!!!! error, OSSemPend from inside critical region\n");
		for(i=0; i<inCritical; i++)
			printf(" [%d] @ %s:%d\n",i,lastEnterCriticalFile[i],lastEnterCriticalLine[i]);
		breakpoint();
		wasCritical=1;
		OS_EXIT_CRITICAL();
	}
#endif

	sem_wait((sem_t*)pevent);

	if(timeout)
	{
		if(i.timeout)
		{
			kill(timeoutCheckId);
			if(err)
				*err=OS_NO_ERR;
		}else{
			if(err)
				*err=OS_TIMEOUT;
		}
	}else{
			if(err)
				*err=OS_NO_ERR;
	}

#if 0
	if(wasCritical)
		OS_ENTER_CRITICAL();
#endif
}

UI8 OSSemPost(OS_EVENT *pevent)
{
#if 0
	int wasCritical=0;
	if(inCritical)
	{
		int i;
		printf("!!!!!!!!!!!!!!!! error, OSSemPost from inside critical region\n");
		for(i=0; i<inCritical; i++)
			printf(" [%d] @ %s:%d\n",i,lastEnterCriticalFile[i],lastEnterCriticalLine[i]);
		breakpoint();
		wasCritical=1;
		OS_EXIT_CRITICAL();
	}
#endif
	if(((sem_t*)pevent)->value<0xffffffff)
	{
		sem_post((sem_t*)pevent);
#if 0
		if(wasCritical)
			OS_ENTER_CRITICAL();
#endif
		//enableInterupt(INT_T1|INT_T3);
		return OS_NO_ERR;
	}else{
#if 0
		if(wasCritical)
			OS_ENTER_CRITICAL();
#endif
		//enableInterupt(INT_T1|INT_T3);
		return OS_SEM_OVF;
	}
}

void OSTaskDel(int id)
{
	if(id==OS_PRIO_SELF)
		threadExit();
}

#ifdef ISS_OS

//static UI32 timerThreadId;
//timerThreadId

typedef struct tag_task_entry {
    UBYTE prio;
    int stacksize;
    char* name;
} task_entry;

task_table task_entry[MAX_TASKS] = {
};


OSLookupTask()
{
}

UBYTE OSTaskCreate(void (OS_FAR *task)(void*), void* pdata, void* pstk, UBYTE prio)
{
    UI32 taskId;
    UI32* pTaskId = NULL;
    char* taskName = NULL;
    long stacksize = 25000;

	printf("OSTaskCreate()\n");

    switch (prio) {
    case PRI_TIMER:
        taskName = "timerTask";
        pTaskID = &timerThreadId;
        break;
    case PRI_PPP0:
        taskName = "pppMain";
        stacksize = 15000;
//        pTaskID = &pppThreadId;
        break;
    case PRI_ETH:
        taskName = "EthTask";
        stacksize = 80000;
        break;
    case PRI_PPP0:
        break;
    }
    taskId = newThreadName(task, stacksize, pdata, taskName);
    if (pTaskId =! NULL) *pTaskId = taskId;
    return taskId;
}

OSTaskResume(UBYTE prio)
{
    PROC_CONTEXT *p;

    switch (prio) {
    case PRI_TIMER:
        p = getThread(timerThreadId);
        break;
    }
    ADL_ithreadWake(p);
}


#else

UBYTE OSTaskCreate(void (OS_FAR *task)(void*), void* pdata, void* pstk, UBYTE prio)
{
	printf("OSTaskCreate()\n");
}

#endif


/*################## AVOS ###################*/
//int main() {}


void panic(char * msg)  /* panic message */
{
	DbgPrint(msg);
	DbgPrint("\n");
}


/*-------- time management --------*/
int clk_stat()
{}


void msleep(ULONG time)
{
	sleep(time);
}

// Time diff in jiffys, between system time in jiffys and time in jiffys
// !!!NOTICE!!!
// If time is newer than system time this functions returns a value > 0
// If time is older than system time this functions returns a value < 0 (NEGATIVE VALUE)
long diffJTime(ULONG time)
{
	return diffKtime(time);
}

// Get system time in jiffys, which is a system clock tick
ULONG jiffyTime()
{
	return Ktime();
}


int gettime(struct tm * time)   /* standard ANSI C */
{}

#if HAVE_ANSI_TIME==0

// This function returns time in seconds since the first call.
// It's used but the ARP protocol in netether.c
// You can remove this if your OS/COMPILER already supports standard ANSI time functions
// or if you don't wish to use the ethernet feature of this stack.
// This functions uses mtime()!
ULONG time(ULONG *returnTime)
{
  static ULONG timeStorage = 0;
  static ULONG lastTime = 0;
  ULONG newTime;
  ULONG timeDiff;

  if (timeStorage)
  {
    // Get new system time
    newTime = mtime();

    // Get time difference in seconds since last call
    if (newTime >= lastTime)
      timeDiff = (newTime - lastTime) / 1000;
    else
      timeDiff = (0 - lastTime + newTime) / 1000;

    // If time difference is more than one second since last called, update time
    if (timeDiff >= 1 )
    {
      timeStorage += timeDiff;
      lastTime    += timeDiff * 1000;
    }
  }
  else
  {
    lastTime = mtime();
    timeStorage = 1;
  }

  if (returnTime) *returnTime = timeStorage;
  return timeStorage;
}
#endif



/*-------- random number generation --------*/
//void magicInit() {}
//ULONG magic() {}
//void avRandomize() {}

void delay(int milliseconds) {}


/*-------- device I/O --------*/

void nGet(int fd, NBuf **returnHere, long delay)
{
	long time=mtime();
	long l,c;
	struct timeval timeout;
	NBuf *curNBuf,*head;

	do{
		nGET(curNBuf);
	}while(!curNBuf);

	curNBuf->len = 0;
	curNBuf->nextBuf = NULL;
	head=curNBuf;

	l=ioctl(fd,IOCTL_PENDINGDATALENGTH,0);
	if(l==0)
	{
		// no data pending, block reading as much as we can...
		l=read(fd,&curNBuf->data[0],NBUFSZ);
		curNBuf->len=l;
		c=l;
	}else{
		c=0;
		while(l)
		{
			if(l<=NBUFSZ)
			{
				read(fd,&curNBuf->data[0],l);
				curNBuf->len=l;
				c+=l;
				l=0;
			}else{
				read(fd,&curNBuf->data[0],NBUFSZ);
				curNBuf->len=NBUFSZ;
				l-=NBUFSZ;
				c+=NBUFSZ;
				do{
					nGET(curNBuf->nextBuf);
				}while(!curNBuf->nextBuf);
				curNBuf=curNBuf->nextBuf;
				curNBuf->nextBuf=NULL;
				curNBuf->len=0;
			}
		}
	}

//	printf("---nGet() got %ld bytes\n",c);

	head->chainLen=c;
	*returnHere=head;
	return;
}

void nPut(int fd, NBuf *head)

{
	NBuf *nb;

//	printf("!!!!nPut()\n");
	for(nb=head; nb; nb=nFree(nb))
	{
		write(fd,nb->data,nb->len);
	}
}

int ISS_userAbort(void)
{
	return 0;
}

#include "../netdebug.h"

void setTraceLevel(int level, TraceModule tMod)
{

}

int getTraceLevel(TraceModule tMod)
{
}

#define OUTPUT_DEBUG 1

/*
 *	trace - a form of printf to send tracing information to stderr
 */
void trace(int level, const char FAR *fmt,...)
{
#if OUTPUT_DEBUG
	va_list a;
	printf("[%d@%ld]:",threadId(),mtime());

	va_start(a,fmt);
	vprintf(fmt,a);
	va_end(a);
	printf("\n");
#endif
}

#if ETHER_SUPPORT
extern int ethRun;
extern int EthPID;
Interface EthAdaptor;

void EthernetInterupt(UI32 cpuID)
{
	KDbgPrint("ethernet intetupt\n");
	EthAdaptor.interrupt(&EthAdaptor);
}

static void LS808_HostInit()
{
	UI32 v;
	printf("ls808Init\n");
		//WRREG(SOFTWARE_CHIP_RESET_REG,(1<<9));

		v=RDREG(GENIODVD_TRI_STATE_ENABLE_REG);
		v=v|(1<<21)|(1<<20)|1;
    WRREG(GENIODVD_TRI_STATE_ENABLE_REG,v);
		v=RDREG(GENIODVD_WRITE_DATA_REG);
    WRREG(GENIODVD_WRITE_DATA_REG,v&~1);
   WRREG(GENIODVD_WRITE_DATA_REG,v|0x1);
    WRREG(GENIOHST_TRI_STATE_ENABLE_REG,0);
#if CLOCK_120MHZ
    WRREG(HST_DEV_2_CTRL_REG,0x240363);
    //WRREG(HST_DEV_2_CTRL_REG,0x240151);
#else
    WRREG(HST_DEV_2_CTRL_REG,0x240242);
#endif
}

// NOTE: This is actually Steve Barne's MAC address +1
const char myMacAddress[]={ 0x00,0xe0,0x98,0x7e,0x78,0x65 };
//const char myMacAddress[]={ 0x66,0x78,0x7e,0x98,0xe0,0x00 };

void Ne2kSetMacAddress(const u_char *address);
void Ne2kProcessInterrupts(void);

void StartEthernet(etherSetup *pSetup)
{
	etherSetup setup;
	UI32 t;

	struct hostent *hp = NULL;

	printf("StartEthernet()\n");

	LS808_HostInit();
	t=Ktime();
	while(abs(diffKtime(t))<5);

	if(pSetup==NULL)
	{
		// Default setup...
		setup.arpExpire = 20000;
		memcpy(&setup.hardwareAddr, &myMacAddress, sizeof(setup.hardwareAddr));
		memcpy(&setup.localAddr, &myIP, sizeof(setup.localAddr));
#if 1
		setup.localAddr = ntohl(inet_addr("195.184.237.109"));
		setup.subnetMask = ntohl(inet_addr("255.255.255.248"));
		setup.gatewayAddr = ntohl(inet_addr("195.184.237.105"));
#else
		setup.localAddr = ntohl(inet_addr("192.168.1.95"));
		setup.subnetMask = ntohl(inet_addr("255.255.255.0"));
		setup.gatewayAddr = ntohl(inet_addr("192.168.1.254"));
#endif
		pSetup=&setup;
	}

	// ADL2001 doesn't have a network bootrom, so we override the MAC address with our own value...
	Ne2kSetMacAddress(pSetup->hardwareAddr);
	Ne2k_DriverEntry(&EthAdaptor);

	ipSetDefault(htonl(pSetup->localAddr), 0, IFT_ETH, 0);
	etherInit();
	ethInit(&EthAdaptor);
	etherConfig(pSetup);
	arpInit();

	// Install the interupt handler for ethernet IRQ...
	gpioInteruptInstall(GPIO_IRQ_ETHERNET,GIM_LEVEL,(GPIO_INTERUPT_FUNCTION)Ne2kProcessInterrupts);
	gpioEnable(GPIO_IRQ_ETHERNET);
	sched_yield();

	// Install timer...
	installInteruptHandler(UINT_T3, (INTERUPT_FUNCTION) timerCheck);
}

void StopEthernet(void)
{
	printf("StopEthernet\n");
	EthAdaptor.stop();
	kill(EthPID);
	pthread_join(EthPID,NULL);
	gpioDisable(GPIO_IRQ_ETHERNET);
	printf("StopEthernet:done\n");
}

#endif

static int hostTransferCompleted(void)
{
	return (RDREG(HST_CONTROL_REG)&(1<<20));
}

#define NET_DEVICE_HOSTID 1

void outw(UI32 addr,UI16 v)
{
	v=(v>>8)|((v&0xff)<<8);
	WRREG(HST_START_ADDR_REG,addr|0x800000);
	WRREG(HST_DATA_WRITE_PORT_REG,v);
	WRREG(HST_CONTROL_REG,0x50000);
	while(!(RDREG(HST_CONTROL_REG)&0x100000));
}

UI16 inw(UI32 addr)
{
	UI16 v;
	WRREG(HST_START_ADDR_REG,addr|0x800000);
	WRREG(HST_CONTROL_REG,0x70000);
	while(!(RDREG(HST_CONTROL_REG)&0x100000));
	v=RDREG(HST_DATA_READ_PORT_REG);
	v=(v>>8)|((v&0xff)<<8);
	return v;
}

void outb(UI32 addr, UI8 v)
{
	WRREG(HST_START_ADDR_REG,addr|0x800000);
	WRREG(HST_DATA_WRITE_PORT_REG,v);
	WRREG(HST_CONTROL_REG,0x50000);
	while(!(RDREG(HST_CONTROL_REG)&0x100000));
}

UI8 inb(UI32 addr)
{
	WRREG(HST_START_ADDR_REG,addr|0x800000);
	WRREG(HST_CONTROL_REG,0x70000);
	while(!(RDREG(HST_CONTROL_REG)&0x100000));
	return RDREG(HST_DATA_READ_PORT_REG);
}

void StartupNet(void)
{
	printf("StartupNet()\n");
	// Install an interupt handler for the network timer stuff...
	disableInterupt(INT_T3);
	installInteruptHandler(UINT_T3, (INTERUPT_FUNCTION) timerCheck);
	netInit();
	enableInterupt(INT_T3);
#if ETHER_SUPPORT
	StartEthernet(NULL);
	printf("ethernet startup done\n");
#endif

	printf("MSPERTICK=%ld\n",MSPERTICK);
	printf("TICKSPERSEC=%ld\n",TICKSPERSEC);
//    socketInit();
}
